package model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;

import processing.AbstractTokenProcessor;

import util.reflection.Reflection;
import util.resources.ResourceManager;

import model.semantics.AbstractToken;
import model.semantics.OperandToken;

/**
 * Parses the expression using reflection and resource files to maximize extendibility.
 * 
 * @author Troy Ferrell
 * @author Anne Weng
 *
 */
public class PicassaParser extends AbstractParser 
{
	private static final String RIGHT_PAREN = ")";
	private static final String LEFT_BRACKET = "[";
	private static final String RIGHT_BRACKET = "]";
	private static final String COLOR_FUNCTION = "COLOR (";
	private static final String NOT_EXCLAMATION = "!";
	private static final String NOT_TILDE = "~";
	private static final String TOKENIZER_DELIMITER = "\\s+"; 

	private static Map<String, AbstractToken> TOKEN_CLASSES = new HashMap<String, AbstractToken>();	
	private static Map<String, AbstractTokenProcessor> TOKEN_PROCESSORS = new HashMap<String, AbstractTokenProcessor>();

	private static final String OPERAND_TOKEN = "model.semantics.OperandToken";
	private static final String OPERAND_TOKEN_PROCESSOR = "processing.OperandTokenProcessor";

	private Stack<AbstractToken> operands = new Stack<AbstractToken>();
	private Stack<AbstractToken> operators = new Stack<AbstractToken>();

	/**
	 * Build the Operators map from a given resource file.
	 * @param resourceFile
	 */
	public PicassaParser(ArrayList<String> resourceFiles) 
	{
		ResourceManager resources = ResourceManager.getInstance();

		resources.addResourcesFromFile(resourceFiles.get(0));
		Map<String, Integer> tokenPrecedences = new HashMap<String, Integer>();
		for(String key : resources)
			tokenPrecedences.put(key, resources.getInteger(key));

		resources.addResourcesFromFile(resourceFiles.get(1));
		for(String key : resources)
			TOKEN_CLASSES.put(key, 
					(AbstractToken)Reflection.createInstance(resources.getString(key), tokenPrecedences.get(key)));

		resources.addResourcesFromFile(resourceFiles.get(2));
		for(String key : resources)
			TOKEN_PROCESSORS.put(key, (AbstractTokenProcessor)Reflection.createInstance(resources.getString(key)));

		TOKEN_PROCESSORS.put(OPERAND_TOKEN_PROCESSOR, 
				(AbstractTokenProcessor)Reflection.createInstance(OPERAND_TOKEN_PROCESSOR));
	}

	/* (non-Javadoc)
	 * @see model.AbstractParser#parseFromInfix(java.lang.String)
	 */
	@Override
	public Stack<AbstractToken> parseFromInfix(String infixString) 
	{
		String[] inputTokens = preprocess(infixString);

		for(String token : inputTokens)
		{	
			if(token.equals(""))
				continue;

			AbstractTokenProcessor tokenProcessor = TOKEN_PROCESSORS.get(token);

			if(tokenProcessor != null)
			{
				tokenProcessor.process(TOKEN_CLASSES.get(token), operators, operands);
			}
			else // Handle operand
			{
				tokenProcessor = TOKEN_PROCESSORS.get(OPERAND_TOKEN_PROCESSOR);

				// TODO: need to process exception handling of gray value
				double grayVal = Double.parseDouble(token);

				OperandToken operand = (OperandToken)Reflection.createInstance(OPERAND_TOKEN, 0);
				operand.setColor(new RGBColor( grayVal ) );

				tokenProcessor.process(operand, operators, operands);
			}
		}

		return mergeStacks(operands,operators);
	}

	/**
	 * Returns the result of merging two stacks together with stack1 on the bottom.
	 * @return
	 */
	private Stack<AbstractToken> mergeStacks(Stack<AbstractToken> stack1, Stack<AbstractToken> stack2)
	{
		while(!stack2.isEmpty())
			stack1.push(stack2.pop());

		return stack1;
	}

	/* (non-Javadoc)
	 * @see model.AbstractParser#preprocess(java.lang.String)
	 */
	@Override
	protected String[] preprocess(String stringToProcess) 
	{
		String temp = stringToProcess;

		//Replace all exclamation marks with tildes so they can be processed using our resource files.
		temp = temp.replace(NOT_EXCLAMATION, NOT_TILDE);

		// Replace color indicators (brackets) with a color "function"
		temp = temp.replace(LEFT_BRACKET, COLOR_FUNCTION);
		temp = temp.replace(RIGHT_BRACKET, RIGHT_PAREN);

		return temp.split(TOKENIZER_DELIMITER);
	}
}
